/*  -*- mode: c++; coding: utf-8; c-file-style: "stroustrup"; -*-

    Copyright 2008 Asier Aguirre <asier.aguirre@gmail.com>
    This file is part of Intromit.

    Intromit is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Intromit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Intromit.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "render_obj.hh"
#include "render_params.hh"
#include "render_attr.hh"
#include "render_stats.hh"

#include <math.h>

#define RO_VISIBILITY_VOLUM    1.8  // the higher, the safer and slower
#define RO_VISIBILITY_NEAR     1.1  // the higher, the safer

CLASS_INIT(render_obj); // RENDER_OBJ CLASS

render_obj::render_obj()
{
    CLASS_CTOR(render_obj);
    ctor();
}

render_obj::~render_obj()
{
    CLASS_DTOR(render_obj);
    dtor();
}

void render_obj::ctor()
{
    // inhibit modelview reference counter
    modelview.declared_local_or_member();
    modelview_required=true;

    // projection transformation
    projection=NULL;

    // render object vector
    util::ctor_vector(ro, n_ro, max_ro);

    // render attributes
    attr=NULL;

    // render results
    required_pass=0;
    forced_pass=RENDER_PASS_LAST;
}

void render_obj::dtor()
{
    // projection transformation
    if(projection) projection=(matrix4x4*)projection->decref();

    // render object vector
    for(int i=0; i<n_ro; i++) if(ro[i]) ro[i]->decref();
    util::dtor_vector(ro, n_ro, max_ro);

    // render_attributes
    if(attr) attr=(render_attr*)attr->decref();
}

bool render_obj::init(render_mng *ctx)
{
    bool dev=true;
    for(int i=0; i<n_ro; i++) dev|=ro[i]->init(ctx);

    return dev;
}

void render_obj::render(render_mng *ctx)
{
    render_init(ctx);
    render_children(ctx);
    render_finish(ctx);
}

void render_obj::render_init(render_mng *ctx)
{
    if(ctx->get_pass()==RENDER_PASS_SELECT)
    {
	glPushName((GLuint)this);
    }
    else
    {
	// increase forced pass
	for(int i=forced_pass; i<ctx->get_pass(); i++)
	    if(!(required_pass & (1<<i))) inc_forced_pass(); else break;
	if(forced_pass==ctx->get_pass()) inc_forced_pass();

	if(ctx->get_pass()==RENDER_PASS_PRECALC)
	{
	    // init results
	    required_pass=0;
	}
    }

    // modelview
    if(modelview_required)
    {
	glPushMatrix();
	glMultMatrixd(modelview.get_coefs());
    }

    // projection
    if(projection)
    {
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glMultMatrixd(projection->get_coefs());
	glMatrixMode(GL_MODELVIEW);
    }

    // hierarchy
    ctx->hierarchy_enter(this);

    // stats
    render_stats *stats;
    if((stats=ctx->get_stats()))
	stats->inc_render_init(ctx->get_pass());
}

void render_obj::render_finish(render_mng *ctx)
{
    // hierarchy
    ctx->hierarchy_leave();

    // projection
    if(projection)
    {
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
    }

    // modelview
    if(modelview_required) glPopMatrix();

    // pop name
    if(ctx->get_pass()==RENDER_PASS_SELECT) glPopName();
}

void render_obj::render_children(render_mng *ctx)
{
    if(ctx->get_pass()==RENDER_PASS_SELECT)
    {
	// select pass
	for(int i=0; i<n_ro; i++)
	    if(ro[i]->required_pass & ctx->get_pass_mask()) ro[i]->render(ctx);
    }
    else if(ctx->get_pass()==RENDER_PASS_PRECALC)
    {
	// precalc render
	for(int i=0; i<n_ro; i++)
	{
	    ro[i]->render(ctx);
	    required_pass|=ro[i]->required_pass;
	    if(forced_pass>ro[i]->forced_pass) forced_pass=ro[i]->forced_pass;
	}
    }
    else
    {
	// normal pass
	for(int i=0; i<n_ro; i++)
	{
	    if((ro[i]->required_pass & ctx->get_pass_mask()) &&
	       (ro[i]->forced_pass<=ctx->get_pass() || ctx->get_forced_render()))
	    {
		ro[i]->render(ctx);
		if(forced_pass>ro[i]->forced_pass) forced_pass=ro[i]->forced_pass;
	    }
	    else if(ro[i]->forced_pass==ctx->get_pass()) ro[i]->inc_forced_pass();
	}
    }
}

bool render_obj::set_hierarchical_forced_pass(render_pass pass, render_obj *obj)
{
    if(obj==this)
    {
	if(forced_pass>pass) forced_pass=pass;
	return true;
    }

    bool dev=false;
    for(int i=0; i<n_ro; i++)
    {
	if(ro[i]->set_hierarchical_forced_pass(pass, obj))
	{
	    if(forced_pass>pass) forced_pass=pass;
	    dev=true;
	}
    }
    return dev;
}

bool render_obj::event(render_mng *ctx)
{
    status^=ctx->get_event_last();

    // always first call to base event method
    //     render_obj::event(ctx);

    // then check render IS_STATUS_* event with if(ctx->get_event_last())

    // then check action with:
    //     event_mng *e=ctx->get_event();
    //     unsigned action;
    //     if((action=e->get_action_last()))

    // and then OGL_*_EVENT with if((event=e->get_event_last()))

    return false; // not managed
}

unsigned render_obj::get_type()
{
    return IS_TYPE_BASE;
}

void render_obj::select_always(float z)
{
    // always select
    glPushMatrix();
    glLoadIdentity();
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();

    glBegin(GL_POINTS);
    glVertex3d(0, 0, 1);
    glEnd();

    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
}

double render_obj::get_modelview_distance_nsqr()
{
    double c[16];
    glGetDoublev(GL_MODELVIEW_MATRIX, c);
    return RO_MODULE_NSQR(&c[12]);
}

bool render_obj::modelview_projection_visibility(double obj_radius, double mv[IS_MAT4X4])
{
    // get column 3 reference of modelview
    double *mv_col3, mv_[IS_MAT4X4];
    if(mv) mv_col3=&mv[12];
    else
    {
	get_modelview_matrix(mv_);
	mv_col3=&mv_[12];
    }

    // first calculations with modelview matrix (last column only)
    if(mv_col3[IS_Z]>obj_radius) return false;
    else if(mv_col3[IS_Z]>=0)
    {
	// obj center is behind camera, but not much
	obj_radius*=RO_VISIBILITY_NEAR;
	if(fabs(mv_col3[IS_X])<obj_radius && fabs(mv_col3[IS_Y])<obj_radius)
	    return true;
	else
	    return false;
    }
    else obj_radius*=RO_VISIBILITY_VOLUM; // correct error due to volumetry of objects

    // store projection matrix
    double m[IS_MAT4X4]; // projection matrix(16)
    glGetDoublev(GL_PROJECTION_MATRIX, m);

    // multiply projection by que vector resulting of multipliying
    // modelview matrix by [0, 0, 0, 1] (last column of modelview matrix),
    // and store in r
    double r[4];
    matrix4x4::vect_mult(m, mv_col3, r);

    // make checking
    if(fabs(r[0])>r[3]+obj_radius || 
       fabs(r[1])>r[3]+obj_radius ||
       r[2]>r[3]+obj_radius/r[3]) return false;

    return true;
}

void render_obj::angles_from_vectors(double lookat[IS_3D], double vertical[IS_3D], double angles[IS_SPHERIC])
{
    // azim
    angles[IS_AZIM]=atan2(lookat[2], lookat[0]);

    // elev
    angles[IS_ELEV]=atan2(lookat[1], sqrt(lookat[0]*lookat[0]+lookat[2]*lookat[2]));

    // precalcs (similar to: render_obj_camera::matrix_precalc_trig())
    double prec_c_azim=cos(angles[IS_AZIM]), prec_s_azim=sin(angles[IS_AZIM]);
    double prec_c_elev=cos(angles[IS_ELEV]), prec_s_elev=sin(angles[IS_ELEV]);

    // vertical vector (similar to: render_obj_camera::matrix_get_vertical_vector(c))
    double v2[IS_3D];
    v2[IS_X]=-prec_s_elev*prec_c_azim;
    v2[IS_Y]= prec_c_elev;
    v2[IS_Z]=-prec_s_elev*prec_s_azim;

    // traversal vector (similar to: render_obj_camera::matrix_get_traversal_vector(c))
    double l2[IS_3D];
    l2[IS_X]=prec_s_azim;
    l2[IS_Y]=0;
    l2[IS_Z]=-prec_c_azim;

    // bank
    angles[IS_BANK]=atan2(RO_SCA_PROD(l2, vertical), RO_SCA_PROD(v2, vertical));
}

void render_obj::set_projection(double m[IS_MAT4X4])
{
    if(!projection) projection=new matrix4x4;
    projection->set_coefs(m);
}
